<?php

namespace App\Trip\Orders\BookOrders;

use App\Jobs\Plane\PlaneOrder\ProcessAfterPlaneOrderChangingWaitingConfirmPrice;
use App\Models\Common\Channel\Channel;
use App\Models\Common\Channel\Repositories\Interfaces\ChannelRepositoryInterface;
use App\Models\Plane\PlaneOrders\Repositories\Interfaces\PlaneOrderRepositoryInterface;
use App\Models\Task\SnapTasks\Repositories\Interfaces\SnapTaskRepositoryInterface;
use App\Models\Train\TrainOrders\Repositories\Interfaces\TrainOrdersRepositoryInterface;
use App\Models\Train\TrainOrders\TrainOrder;
use App\Models\Trip\OrderPay\Repositories\Interfaces\OrderPayRepositoryInterface;
use App\Models\Trip\TripOrderCosts\Repositories\Interfaces\TripOrderCostsRepositoryInterface;
use App\Models\User\Repositories\Interfaces\UserRepositoryInterface;
use App\Trip\Enums\Plane\PlaneProviders;
use App\Trip\Orders\BookOrders\Params\AbstractParam\AbstractParam;
use App\Trip\Orders\BookOrders\Params\BookOrderParam;
use App\Trip\Orders\BookOrders\Params\Param;
use App\Trip\Orders\BookOrders\Params\SnapOrderParam;
use App\Trip\Orders\BookOrders\Plane\Order\BookNewPlaneOrder;
use App\Trip\Orders\BookOrders\Plane\Order\ChangeNewPlaneOrder;
use App\Trip\Orders\BookOrders\TaskEdit\TaskEditOrder;
use App\Trip\Orders\BookOrders\Train\Order\BookNewOrder;
use App\Trip\Orders\BookOrders\Train\Order\ChangeNewOrder;
use App\Trip\Orders\BookOrders\Train\Order\SnapNewOrder;
use App\Trip\Payment\PaymentAttach;
use App\Trip\Train\Order\Impl\PiaoChiOrderCancelImpl;
use App\Trip\Train\Order\Impl\SnapTaskLowLevel\SnapTaskLowLevelImpl;
use App\Trip\Wechat\AppId;
use App\Trip\Wechat\Pay\WechatPay;
use App\Trip\Wechat\Traits\getWechatPayInfoTrait;
use Carbon\Carbon;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\DB;
use JetBrains\PhpStorm\ArrayShape;
use Throwable;

final class BookOrdersManager
{
    use getWechatPayInfoTrait;

    private array $orderData;

    private SnapTaskRepositoryInterface       $snapTaskRepository;
    private TrainOrdersRepositoryInterface    $trainOrderRepository;
    private UserRepositoryInterface           $userRepo;
    private TripOrderCostsRepositoryInterface $costRepo;
    private PlaneOrderRepositoryInterface     $planeOrderRepository;
    private ?Channel                          $channel;


    public function __construct(array $orderData)
    {
        $this->orderData            = $orderData;
        $this->snapTaskRepository   = app(SnapTaskRepositoryInterface::class);
        $this->trainOrderRepository = app(TrainOrdersRepositoryInterface::class);
        $this->planeOrderRepository = app(PlaneOrderRepositoryInterface::class);
        $this->userRepo             = app(UserRepositoryInterface::class);
        $this->costRepo             = app(TripOrderCostsRepositoryInterface::class);
    }

    /**
     * @throws GuzzleException
     * @throws Exception|Throwable
     */
    public function plane_book(): array
    {
        $param         = $this->getParam(BookTypes::BOOK_TYPE_BOOK);
        $new_order     = new BookNewPlaneOrder($param->getBookPlaneOrderParam(), $param);
        $order         = $new_order->buildOrderInfo();
        $price_details = [
            "total_price" => $order["price_details"]['total_price'],
            'trips'       => [
                $order['price_details'],
            ],
        ];
        //  创建飞机订单
        $order_info = null;
        DB::transaction(function () use ($order, &$order_info, $param) {
            $order_info = $this->planeOrderRepository->createPlaneOrder($order);
            // 下单服务商
            $orderService = PlaneProviders::from($order_info->order_service)->getPlaneOrderStrategy();
            // 开始下单
            $bookRe    = $orderService->book($order_info, [
                'bookingTagKey' => $this->orderData['bookingTagKey'],
                'passengers'    => $param->getPassengersParam()->getParamPassengers(),
                'out_order_sn'  => $order_info->getOutOrderSn(),
            ]);
            $subOrders = $bookRe->subOrders();
            $this->planeOrderRepository->saveOutOrderId($order_info, $bookRe->getOutOrderId(), $subOrders);
        });

        return compact('order_info', 'price_details');
    }

    private function getParam(BookTypes $bookType): AbstractParam
    {
        return new Param($this->orderData, $bookType);
    }

    /**
     * 下单
     *
     * @throws Exception
     * @throws GuzzleException
     */
    public function book(): array
    {
        extract($this->before_book_price_details());
        $this->trainOrderRepository->createTrainOrders($orders);
        return $orders;
    }

    /**
     * 订票前价格明细
     *
     * @throws Exception
     * @throws GuzzleException
     */
    #[ArrayShape([
        'orders'        => 'array',
        'price_details' => 'array',
    ])]
    public function before_book_price_details(): array
    {
        $param  = $this->getParam(BookTypes::BOOK_TYPE_BOOK);
        $orders = [];
        /**
         * @var  BookOrderParam $order
         * 构造订单数据
         */

        $price_details = [
            "total_price" => 0,
            'trips'       => [],
        ];
        foreach ($param->getBookOrdersParam()->getOrders() as $order) {
            if (!$order->getTripType()->isTrain()) {
                throw new Exception("仅支付火车票预定");
            }
            $new_order                    = new BookNewOrder($order, $param);
            $build                        = $new_order->buildOrderInfo();
            $price_details['total_price'] += $build["price_details"]['total_price'];
            $price_details['trips'][]     = [
                "total_price" => $build["price_details"]['total_price'],
                'trips'       => [
                    $build['price_details'],
                ],
            ];
            $orders[]                     = $build;
        }
        return compact('orders', 'price_details');
    }

    /**
     * @throws GuzzleException
     * @throws Exception|Throwable
     */
    public function plane_change(): array
    {
        $param             = $this->getParam(BookTypes::BOOK_TYPE_CHANGE);
        $origin_order_info = $this->planeOrderRepository->getOrderInfo($param->getOriginOrderSn());
        if (!$origin_order_info || $origin_order_info->getUserId() != $param->getUserId()) {
            throw new Exception("订单不存在", 404);
        }

        $change_order  = new ChangeNewPlaneOrder($param->getChangePlaneOrderParam($origin_order_info), $param, $origin_order_info);
        $order         = $change_order->buildOrderInfo();
        $price_details = [
            "total_price" => $order["price_details"]['total_price'],
            'trips'       => [
                $order['price_details'],
            ],
        ];
        //  创建订单
        DB::transaction(function () use ($order, $origin_order_info, &$order_info) {
            $order_info = $this->planeOrderRepository->changePlaneOrder($origin_order_info, $order);
        });

        // 订单创建成功,如果价格待确认
        if (!$order_info->isProviderPriceConfirmed()) {
            ProcessAfterPlaneOrderChangingWaitingConfirmPrice::dispatchSync($order_info);
        }
        return compact('order_info', 'origin_order_info', 'price_details');
    }

    /**
     * 火车票改签
     *
     * @throws Exception
     * @throws GuzzleException|Throwable
     */
    public function train_change(): array
    {

        $changeData = $this->build_train_change_data();
        extract($changeData);
        $order_info = null;
        //  创建订单
        DB::transaction(function () use ($order, $origin_order_info, &$order_info) {
            $order_info = $this->trainOrderRepository->changeTrainOrder($origin_order_info, $order);
        });
        return compact('order_info', 'origin_order_info', 'price_details');
    }

    /**
     *
     *
     * @param bool $only_query_price 改签钱前价格计 减少一些其他计算
     *
     * @return array
     * @throws Exception
     * @throws GuzzleException
     */
    #[ArrayShape([
        'order'             => 'array',
        'price_details'     => 'array',
        'origin_order_info' => TrainOrder::class,
    ])]
    public function build_train_change_data(bool $only_query_price = false): array
    {
        $param             = $this->getParam(BookTypes::BOOK_TYPE_CHANGE);
        $origin_order_info = $this->trainOrderRepository->getOrderInfo($param->getOriginOrderSn());
        if (!$origin_order_info || $origin_order_info->getUserId() != $param->getUserId()) {
            throw new Exception("订单不存在", 404);
        }

        $change_order  = new ChangeNewOrder($param->getChangeOrderParam($origin_order_info), $param, $origin_order_info);
        $order         = $change_order->buildOrderInfo();
        $price_details = [
            "total_price" => $order["price_details"]['total_price'],
            "tip"         => $order["price_details"]['tip'],
            'trips'       => [
                $order['price_details'],
            ],
        ];

        return compact('order', 'price_details', 'origin_order_info');
    }

    /**
     * @throws Exception|Throwable
     */
    public function snap_up(): TrainOrder
    {

        $param = $this->getParam(BookTypes::BOOK_TYPE_SNAP_UP);
        /**
         * @var  SnapOrderParam $order
         * 构造订单数据
         */

        $snap_order_param = $param->getSnapOrderParam();
        $snapNewOrder     = new SnapNewOrder($snap_order_param, $param);
        $snap_order       = $snapNewOrder->buildOrderInfo();
        $snap_param       = $snap_order_param->getSnapParam();
        $task_data        = [
            "task_id"     => $snap_order_param->getTripId(),
            "snap_level"  => $snap_param->getSnapLevel(),
            "auto_ticket" => 1,
            "stop_time"   => $snap_param->getStopTime(),
            "trip_id"     => $snap_order_param->getTripId(),
            "user_id"     => $param->getUserId(),
        ];

        $snap_order_info = null;
        DB::transaction(function () use ($snap_order, $task_data, &$snap_order_info) {
            $task            = $this->snapTaskRepository->addTask($task_data);
            $snap_order_info = $this->trainOrderRepository->createSnapTaskOrder($snap_order, $task);
        });
        return $snap_order_info;
    }

    /**
     * @throws InvalidArgumentException
     * @throws Exception
     */
    public function task_edit(): array
    {
        $taskEditOrder = $this->getTaskEditOrder();
        $task_info     = $taskEditOrder->getTaskInfo();
        if (!$task_info->canEditTask()) {
            throw new Exception("任务不可编辑");
        }
        $task_order_info = $task_info->getTrainOrderInfo();
        if ($taskEditOrder->isEditNotChange()) {
            return [
                'order_info' => $task_order_info,
                'pay_info'   => [],
                'desc'       => '嘿嘿,没变化',
            ];
        }
        // 此处需要先停止任务
        // 如果停止成功 往下执行
        // 如果停止失败 , 如:出票中/出票待确认 高速的调用 取消订单 . 低速需要停止任务
        // TODO
        if ($task_order_info->getOutOrderId()) {
            $cancel_order = new PiaoChiOrderCancelImpl($task_order_info);
            $cancel_res   = $cancel_order->cancel();
            if (!$cancel_res->isSuccess()) {
                throw new Exception($cancel_res->getMessage());
            }
        } else {
            $tmp = new  SnapTaskLowLevelImpl($task_info->getTaskId(), 'stop');
            $tmp->stop();
        }

        $edit_pay = $taskEditOrder->getEditPay();
        if ($edit_pay->getNeedPayAmount() > 0) {
            $channelRepo     = app(ChannelRepositoryInterface::class);
            $orderPayRepo    = app(OrderPayRepositoryInterface::class);
            $this->appId     = AppId::getAppId();
            $this->wechatPay = new WechatPay(['appid' => $this->appId]);
            $order_pay_info  = $orderPayRepo->createTrainSnapTaskEditPay($task_order_info,
                $taskEditOrder,
                $this->wechatPay,
                $channelRepo->findChannelByAppId($this->appId)
            );

            // 暂时不考虑单独支付
            $out_trade_no = $order_pay_info->getPaySn();
            $description  = $order_pay_info->getPayTitle();
            $time_expire  = Carbon::now()->addHour()->toRfc3339String();

            /**
             * 构造 attach 返回用于判别 发起支付的 级别 如果是飞机 则飞机可以独立支付
             *
             * @var $attach string type =>trip_type,os =>order_sn
             */
            $attach   = new PaymentAttach([
                'type'     => PaymentAttach::PAY_TYPE_SNAP_TASK_EDIT,  //编辑任务
                'order_sn' => $task_order_info->getOrderSn(),
                'task_id'  => $task_order_info->getTaskId(),
            ]);
            $payData  = [
                "out_trade_no" => $out_trade_no,
                "description"  => $description,
                "time_expire"  => $time_expire,
                "order_amount" => $order_pay_info->getPayAmount(),
                "attach"       => $attach,
            ];
            $pay_info = $this->get_pay_info($payData);

        } else {
            $pay_info = [];
            // 执行编辑操作 这里只更新乘客的票价
            $this->trainOrderRepository->updateSnapOrder($task_order_info, $taskEditOrder);
        }

        return [
            'order_info' => $task_order_info,
            'pay_info'   => $pay_info,
        ];
    }

    /**
     * 编辑抢票任务
     *
     * @throws Exception
     */

    public function getTaskEditOrder(): TaskEditOrder
    {
        $param = $this->getParam(BookTypes::BOOK_TYPE_SNAP_UP_EDIT);
        return new TaskEditOrder($param);
    }
}